package connection;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import util.CryptGame;
import util.PacketStream;

public class GameConnection extends Thread {
	private byte[] cryptkey = { (byte) 0x94, (byte) 0x35, (byte) 0x00,
			(byte) 0x00, (byte) 0xa1, (byte) 0x6c, (byte) 0x54, (byte) 0x87 };

	LoginResult loginResult = null;
	Socket sock;
	BufferedInputStream in;
	BufferedOutputStream out;
	boolean terminate = false;
	int clientProtocolVersion = 656;
	int charNum = 1;
	CryptGame inCrypt = new CryptGame();
	CryptGame outCrypt = new CryptGame();
	ConnectionEventReceiver connectionEventReceiver;
	GamePackageEventReceiver gamePackageEventReceiver;

	public GameConnection(GamePackageEventReceiver gamePackageEventReceiver,
			ConnectionEventReceiver connectionEventReceiver,
			LoginResult loginResult, int clientProtocolVersion, int charNum) {
		this.gamePackageEventReceiver = gamePackageEventReceiver;
		this.connectionEventReceiver = connectionEventReceiver;
		this.loginResult = loginResult;
		this.clientProtocolVersion = clientProtocolVersion;
		this.charNum = charNum;
	}

	public void fireGame() throws IOException {
		sock = new Socket(loginResult.host.Addr, loginResult.host.port);
		in = new BufferedInputStream(sock.getInputStream());
		out = new BufferedOutputStream(sock.getOutputStream());
		connectionEventReceiver.procConnectionEvent(new Msg(
				Msg.MSG_TYPE.SUCESS, "GAME CONNECTION OK"),
				ENUM_CONECTION_EVENT.EVT_MSG);
	}

	private byte[] buildProtocolVersionPack() throws IOException {
		byte protocolVersionPack[] = { (byte) 0x00, (byte) 0x90, (byte) 0x02,
				(byte) 0x00, (byte) 0x00, (byte) 0x09, (byte) 0x07,
				(byte) 0x54, (byte) 0x56, (byte) 0x03, (byte) 0x09,
				(byte) 0x0b, (byte) 0x01, (byte) 0x07, (byte) 0x02,
				(byte) 0x54, (byte) 0x54, (byte) 0x56, (byte) 0x07,
				(byte) 0x00, (byte) 0x02, (byte) 0x55, (byte) 0x56,
				(byte) 0x00, (byte) 0x51, (byte) 0x00, (byte) 0x53,
				(byte) 0x57, (byte) 0x04, (byte) 0x07, (byte) 0x55,
				(byte) 0x08, (byte) 0x54, (byte) 0x01, (byte) 0x07,
				(byte) 0x01, (byte) 0x53, (byte) 0x00, (byte) 0x56,
				(byte) 0x55, (byte) 0x56, (byte) 0x01, (byte) 0x06,
				(byte) 0x05, (byte) 0x04, (byte) 0x51, (byte) 0x03,
				(byte) 0x08, (byte) 0x51, (byte) 0x08, (byte) 0x51,
				(byte) 0x56, (byte) 0x04, (byte) 0x54, (byte) 0x06,
				(byte) 0x55, (byte) 0x08, (byte) 0x02, (byte) 0x09,
				(byte) 0x51, (byte) 0x56, (byte) 0x01, (byte) 0x53,
				(byte) 0x06, (byte) 0x55, (byte) 0x04, (byte) 0x53,
				(byte) 0x00, (byte) 0x56, (byte) 0x56, (byte) 0x53,
				(byte) 0x01, (byte) 0x09, (byte) 0x02, (byte) 0x09,
				(byte) 0x01, (byte) 0x51, (byte) 0x54, (byte) 0x51,
				(byte) 0x09, (byte) 0x55, (byte) 0x56, (byte) 0x09,
				(byte) 0x03, (byte) 0x04, (byte) 0x07, (byte) 0x05,
				(byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x55,
				(byte) 0x04, (byte) 0x06, (byte) 0x09, (byte) 0x04,
				(byte) 0x51, (byte) 0x01, (byte) 0x08, (byte) 0x08,
				(byte) 0x06, (byte) 0x05, (byte) 0x52, (byte) 0x06,
				(byte) 0x04, (byte) 0x01, (byte) 0x07, (byte) 0x54,
				(byte) 0x03, (byte) 0x06, (byte) 0x52, (byte) 0x55,
				(byte) 0x06, (byte) 0x55, (byte) 0x55, (byte) 0x51,
				(byte) 0x01, (byte) 0x02, (byte) 0x04, (byte) 0x54,
				(byte) 0x03, (byte) 0x55, (byte) 0x54, (byte) 0x01,
				(byte) 0x57, (byte) 0x51, (byte) 0x55, (byte) 0x05,
				(byte) 0x52, (byte) 0x05, (byte) 0x54, (byte) 0x07,
				(byte) 0x51, (byte) 0x51, (byte) 0x55, (byte) 0x07,
				(byte) 0x02, (byte) 0x53, (byte) 0x53, (byte) 0x00,
				(byte) 0x52, (byte) 0x05, (byte) 0x52, (byte) 0x07,
				(byte) 0x01, (byte) 0x54, (byte) 0x00, (byte) 0x03,
				(byte) 0x05, (byte) 0x05, (byte) 0x08, (byte) 0x06,
				(byte) 0x05, (byte) 0x05, (byte) 0x06, (byte) 0x03,
				(byte) 0x00, (byte) 0x0d, (byte) 0x08, (byte) 0x01,
				(byte) 0x07, (byte) 0x09, (byte) 0x03, (byte) 0x51,
				(byte) 0x03, (byte) 0x07, (byte) 0x53, (byte) 0x09,
				(byte) 0x51, (byte) 0x06, (byte) 0x07, (byte) 0x54,
				(byte) 0x0a, (byte) 0x50, (byte) 0x56, (byte) 0x02,
				(byte) 0x52, (byte) 0x04, (byte) 0x05, (byte) 0x55,
				(byte) 0x51, (byte) 0x02, (byte) 0x53, (byte) 0x00,
				(byte) 0x08, (byte) 0x54, (byte) 0x04, (byte) 0x52,
				(byte) 0x56, (byte) 0x06, (byte) 0x02, (byte) 0x09,
				(byte) 0x00, (byte) 0x08, (byte) 0x03, (byte) 0x53,
				(byte) 0x56, (byte) 0x01, (byte) 0x05, (byte) 0x00,
				(byte) 0x55, (byte) 0x06, (byte) 0x08, (byte) 0x56,
				(byte) 0x04, (byte) 0x0d, (byte) 0x06, (byte) 0x07,
				(byte) 0x52, (byte) 0x06, (byte) 0x07, (byte) 0x04,
				(byte) 0x0a, (byte) 0x06, (byte) 0x01, (byte) 0x04,
				(byte) 0x54, (byte) 0x04, (byte) 0x00, (byte) 0x05,
				(byte) 0x02, (byte) 0x04, (byte) 0x54, (byte) 0x00,
				(byte) 0x09, (byte) 0x52, (byte) 0x53, (byte) 0x05,
				(byte) 0x04, (byte) 0x01, (byte) 0x04, (byte) 0x05,
				(byte) 0x05, (byte) 0x01, (byte) 0x52, (byte) 0x51,
				(byte) 0x52, (byte) 0x0d, (byte) 0x06, (byte) 0x51,
				(byte) 0x08, (byte) 0x09, (byte) 0x54, (byte) 0x53,
				(byte) 0x00, (byte) 0x0d, (byte) 0x01, (byte) 0x02,
				(byte) 0x03, (byte) 0x54, (byte) 0x53, (byte) 0x01,
				(byte) 0x05, (byte) 0x03, (byte) 0x08, (byte) 0x56,
				(byte) 0x54, (byte) 0x07, (byte) 0x02, (byte) 0x54,
				(byte) 0x0b, (byte) 0x06 };

		ByteBuffer buf = ByteBuffer.wrap(protocolVersionPack);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(1);
		buf.putInt(clientProtocolVersion);

		return buf.array();
	}

	private byte[] buildAuthLoginPack() throws IOException {
		ByteBuffer buf = ByteBuffer.allocate(41);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(0);
		buf.put((byte) 0x08);

		writeS(buf, loginResult.login);
		buf.putInt(loginResult.playId2);
		buf.putInt(loginResult.playId1);
		buf.putInt(loginResult.loginId1);
		buf.putInt(loginResult.loginId2);

		return buf.array();
	}

	private byte[] builCharSelectPack() throws IOException {
		ByteBuffer buf = ByteBuffer.allocate(19);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(0);
		buf.put((byte) 0x0d);
		buf.putInt(charNum - 1);

		return buf.array();
	}

	private byte[] builEnterWorldPack() throws IOException {
		ByteBuffer buf = ByteBuffer.allocate(17);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(0);
		buf.put((byte) 0x03);
		return buf.array();
	}

	private byte[] builRequestManorList() throws IOException {
		ByteBuffer buf = ByteBuffer.allocate(3);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(0);
		buf.put((byte) 0xd0);
		buf.put((byte) 0x08);
		return buf.array();
	}

	private byte[] builRequestQuestList() throws IOException {
		ByteBuffer buf = ByteBuffer.allocate(1);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(0);
		buf.put((byte) 0x63);
		return buf.array();
	}

	private byte[] builRequestMagicSkillList() throws IOException {
		ByteBuffer buf = ByteBuffer.allocate(12);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(0);
		buf.put((byte) 0x2e);
		buf.put((byte) 0x00);
		buf.put((byte) 0x00);
		buf.put((byte) 0x00);
		buf.put((byte) 0xe1);
		buf.put((byte) 0xf7);
		buf.put((byte) 0x02);
		buf.put((byte) 0x00);
		buf.put((byte) 0xcd);
		buf.put((byte) 0xb2);
		buf.put((byte) 0x60);
		buf.put((byte) 0x4a);
		return buf.array();
	}

	private byte[] builRequestSkillCooltime() throws IOException {
		ByteBuffer buf = ByteBuffer.allocate(1);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(0);
		buf.put((byte) 0x9d);
		return buf.array();
	}

	@Override
	public void run() {
		byte packetData[];

		try {
			if (terminate)
				return;

			byte protocolVersionPack[] = buildProtocolVersionPack();
			PacketStream.writePacket(out, protocolVersionPack);
			packetData = PacketStream.readPacket(in);
			if (packetData == null) {
				System.out.println("Unknow error in protocolVersionPack");
				setTerminate();
				return;
			}
			System.arraycopy(packetData, 2, cryptkey, 0, 4);
			inCrypt.setKey(cryptkey);
			outCrypt.setKey(cryptkey);

			byte authLoginPack[] = buildAuthLoginPack();
			sendPacket(authLoginPack);
			sleep(3000);

			packetData = readPacket();
			if (packetData == null) {
				System.out.println("Unknow error in protocolVersionPack");
				setTerminate();
				return;
			}
			// System.out.print(Printer.printData(packetData,
			// packetData.length,"CHAR SELECTION LIST PACKET"));

			byte charSelectPack[] = builCharSelectPack();
			sendPacket(charSelectPack);
			connectionEventReceiver.procConnectionEvent(
					new Msg(Msg.MSG_TYPE.ATENTION, "SEND SELECT CHAR["
							+ charNum + "]"), ENUM_CONECTION_EVENT.EVT_MSG);

			sleep(5000);

			byte requestManor[] = builRequestManorList();
			sendPacket(requestManor);
			connectionEventReceiver.procConnectionEvent(new Msg(
					Msg.MSG_TYPE.ATENTION, "SEND REQUEST MANOR"),
					ENUM_CONECTION_EVENT.EVT_MSG);

			byte requestQuest[] = builRequestQuestList();
			sendPacket(requestQuest);
			connectionEventReceiver.procConnectionEvent(new Msg(
					Msg.MSG_TYPE.ATENTION, "SEND REQUEST QUEST LIST"),
					ENUM_CONECTION_EVENT.EVT_MSG);

			if (!terminate && (packetData = readPacket()) != null) {
				gamePackageEventReceiver.procGamePackage(packetData);

				byte enterWorldPack[] = builEnterWorldPack();
				sendPacket(enterWorldPack);
				connectionEventReceiver.procConnectionEvent(new Msg(
						Msg.MSG_TYPE.ATENTION, "SEND ENTER GAME WORLD"),
						ENUM_CONECTION_EVENT.EVT_MSG);

				sleep(4000);

				byte RequestMagicSkillList[] = builRequestMagicSkillList();
				sendPacket(RequestMagicSkillList);
				connectionEventReceiver.procConnectionEvent(
						new Msg(Msg.MSG_TYPE.ATENTION,
								"SEND REQUEST MAGIC SKILL LIST"),
						ENUM_CONECTION_EVENT.EVT_MSG);

				byte RequestSkillCooltime[] = builRequestSkillCooltime();
				sendPacket(RequestSkillCooltime);
				connectionEventReceiver.procConnectionEvent(new Msg(
						Msg.MSG_TYPE.ATENTION, "SEND REQUEST SKILL COOLTIME"),
						ENUM_CONECTION_EVENT.EVT_MSG);

				while (!terminate && (packetData = readPacket()) != null) {
					// System.out.println("\nINI =====================================================================");
					// System.out.println("<GAME PACKET>");
					// System.out.print(printData(packetData,
					// packetData.length));
					// System.out.println("\nEND =====================================================================");
					// System.out.println(Printer.printData(packetData,packetData.length,Packets.getServerMessageType(packetData[0],
					// 0)));
					gamePackageEventReceiver.procGamePackage(packetData);
				}

			}

			setTerminate();
		} catch (IOException e) {
			setTerminate();
		} catch (Exception e) {
			setTerminate();
			e.printStackTrace();
		}
	}

	public LoginResult getLoginResult() {
		return loginResult;
	}

	public void setTerminate() {
		this.terminate = true;
		try {
			sock.close();
			in.close();
			out.close();
		} catch (IOException e) {
			// NADA
		}
	}

	public boolean isConnected() {
		return sock.isConnected() && !terminate;
	}

	/**
	 * Read encrypted packet
	 * 
	 * @return
	 * @throws IOException
	 */

	protected void writeS(ByteBuffer buf, String text) {
		try {
			if (text != null) {
				buf.put(text.getBytes("UTF-16LE"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		buf.put((byte) 0x00);
		buf.put((byte) 0x00);
	}

	public final String readS(ByteBuffer buf) {
		StringBuilder sb = new StringBuilder();
		char ch;
		try {
			while ((ch = buf.getChar()) != 0)
				sb.append(ch);
		} catch (Exception e) {
		}
		return sb.toString();
	}

	public byte[] readPacket() throws IOException {
		byte packetData[] = PacketStream.readPacket(in);
		if (packetData == null)
			return null;
		inCrypt.decrypt(packetData);
		return packetData;
	}

	public void sendPacket(byte data[]) throws IOException {
		// System.out.println(Printer.printData(data,data.length,Packets.getClientMessageType(data[0],
		// data[1])));
		if (out == null)
			return;

		outCrypt.encrypt(data);
		PacketStream.writePacket(out, data);
	}

}